package com.lhl.log.autoconfigure.support;

import com.lhl.log.annotation.Log;
import com.lhl.log.annotation.ParamLog;
import com.lhl.log.annotation.ResultLog;
import com.lhl.log.annotation.ThrowingLog;
import com.lhl.log.autoconfigure.Slf4jProperties;
import com.lhl.log.config.Level;
import com.lhl.log.config.Position;
import com.lhl.log.core.LogCallback;
import com.lhl.log.core.LogHandler;
import com.lhl.log.core.MethodInfo;
import com.lhl.log.core.MethodParser;
import com.lhl.log.formatter.ParamFormatter;
import com.lhl.log.formatter.ResultFormatter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * @className: LogProcessor
 * @description: 日志处理器
 * @author: king
 * @date: 2020-09-16 15:04
 **/
@Aspect
@Slf4j
public class LogProcessor {
    @Resource(type = Slf4jProperties.class)
    private Slf4jProperties slf4JProperties;

    /**
     * 打印参数日志
     * @param joinPoint 切入点
     */
    @Before("@annotation(com.lhl.log.annotation.ParamLog)")
    public void beforePrint(JoinPoint joinPoint) {
        if (LogHandler.isEnable(log)) {
            MethodSignature signature  = (MethodSignature) joinPoint.getSignature();
            ParamLog annotation = signature.getMethod().getAnnotation(ParamLog.class);
            MethodInfo methodInfo = this.beforePrint(
                    signature,
                    joinPoint.getArgs(),
                    annotation.paramFilter(),
                    annotation.value(),
                    annotation.level(),
                    annotation.position(),
                    annotation.paramFormatter()
            );
            // 执行回调
            this.callback(annotation.callback(), annotation, methodInfo, joinPoint, null);
        }
    }

    /**
     * 打印返回值日志
     * @param joinPoint 切入点
     * @param result 返回结果
     */
    @AfterReturning(value = "@annotation(com.lhl.log.annotation.ResultLog)", returning = "result")
    public void afterPrint(JoinPoint joinPoint, Object result) {
        if (LogHandler.isEnable(log)) {
            MethodSignature signature  = (MethodSignature) joinPoint.getSignature();
            ResultLog annotation = signature.getMethod().getAnnotation(ResultLog.class);
            MethodInfo methodInfo = this.afterPrint(
                    signature,
                    result,
                    annotation.value(),
                    annotation.level(),
                    annotation.position(),
                    annotation.resultFormatter()
            );
            // 执行回调
            this.callback(annotation.callback(), annotation, methodInfo, joinPoint, result);
        }
    }

    /**
     * 打印异常日志
     * @param joinPoint 切入点
     * @param throwable 异常
     */
    @AfterThrowing(value = "@annotation(com.lhl.log.annotation.ThrowingLog)||@annotation(com.lhl.log.annotation.Log)", throwing = "throwable")
    public void throwingPrint(JoinPoint joinPoint, Throwable throwable) {
        if (LogHandler.isEnable(log)) {
            MethodSignature signature  = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String methodName = method.getName();
            try {
                Annotation annotation;
                String busName;
                Class<? extends LogCallback> callback;
                MethodInfo methodInfo = MethodParser.getMethodInfo(signature, MethodInfo.NATIVE_LINE_NUMBER);
                ThrowingLog throwingLogAnnotation = method.getAnnotation(ThrowingLog.class);
                if (throwingLogAnnotation!=null) {
                    annotation = throwingLogAnnotation;
                    busName = throwingLogAnnotation.value();
                    callback = throwingLogAnnotation.callback();
                }else {
                    Log logAnnotation = method.getAnnotation(Log.class);
                    annotation = logAnnotation;
                    busName = logAnnotation.value();
                    callback = logAnnotation.callback();
                }
                log.error(LogHandler.getThrowingInfo(busName, methodInfo), throwable);
                // 执行回调
                this.callback(callback, annotation, methodInfo, joinPoint, null);
            } catch (Exception e) {
                log.error("{}.{}方法错误", signature.getDeclaringTypeName(), methodName);
                log.error(e.getMessage(), e);
            }
        }
    }

    /**
     * 打印环绕日志
     * @param joinPoint 切入点
     * @return 返回方法返回值
     * @throws Throwable 异常
     */
    @Around(value = "@annotation(com.lhl.log.annotation.Log)")
    public Object aroundPrint(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature  = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        Object result;
        if (LogHandler.isEnable(log)) {
            Log annotation = signature.getMethod().getAnnotation(Log.class);
            this.beforePrint(
                    signature,
                    args,
                    annotation.paramFilter(),
                    annotation.value(),
                    annotation.level(),
                    annotation.position(),
                    annotation.paramFormatter()
            );
            result = joinPoint.proceed(args);
            MethodInfo methodInfo = this.afterPrint(
                    signature,
                    result,
                    annotation.value(),
                    annotation.level(),
                    annotation.position(),
                    annotation.resultFormatter()
            );
            // 执行回调
            this.callback(annotation.callback(), annotation, methodInfo, joinPoint, result);
        }else {
            result = joinPoint.proceed(args);
        }
        return result;
    }

    /**
     * 打印参数日志
     * @param signature 方法签名
     * @param args 参数列表
     * @param filterParamNames 参数过滤列表
     * @param busName 业务名称
     * @param level 日志级别
     * @param position 代码定位开启标志
     * @return 返回方法信息
     */
    private MethodInfo beforePrint(
            MethodSignature signature,
            Object[] args,
            String[] filterParamNames,
            String busName,
            Level level,
            Position position,
            Class<? extends ParamFormatter> paramFormatter
    ) {
        Method method = signature.getMethod();
        String methodName = method.getName();
        MethodInfo methodInfo = null;
        try {
            if (position== Position.ENABLED) {
                methodInfo = MethodParser.getMethodInfo(signature.getDeclaringTypeName(), methodName, signature.getParameterNames());
            }else {
                methodInfo = MethodParser.getMethodInfo(signature, MethodInfo.NATIVE_LINE_NUMBER);
            }
            ParamFormatter formatter;
            if (!paramFormatter.getName().equals(ParamFormatter.class.getName())) {
                formatter = LogContext.getContext().getBean(paramFormatter);
            }else {
                formatter = LogContext.getContext().getBean(this.slf4JProperties.getGlobalParamFormatter());
            }
            formatter.format(log, level, busName, methodInfo, args, filterParamNames);
        } catch (Exception e) {
            log.error("{}.{}方法错误", signature.getDeclaringTypeName(), methodName);
            log.error(e.getMessage(), e);
        }
        return methodInfo;
    }

    /**
     * 打印返回值日志
     * @param signature 方法签名
     * @param result 返回结果
     * @param busName 业务名称
     * @param level 日志级别
     * @param position 代码定位开启标志
     * @return 返回方法信息
     */
    private MethodInfo afterPrint(
            MethodSignature signature,
            Object result,
            String busName,
            Level level,
            Position position,
            Class<? extends ResultFormatter> resultFormatter
    ) {
        Method method = signature.getMethod();
        String methodName = method.getName();
        MethodInfo methodInfo = null;
        try {
            if (position==Position.ENABLED) {
                methodInfo = MethodParser.getMethodInfo(signature.getDeclaringTypeName(), methodName, signature.getParameterNames());
            }else {
                methodInfo = MethodParser.getMethodInfo(signature, MethodInfo.NATIVE_LINE_NUMBER);
            }
            ResultFormatter formatter;
            if (!resultFormatter.getName().equals(ResultFormatter.class.getName())) {
                formatter = LogContext.getContext().getBean(resultFormatter);
            }else {
                formatter = LogContext.getContext().getBean(this.slf4JProperties.getGlobalResultFormatter());
            }
            formatter.format(log, level, busName, methodInfo, result);
        } catch (Exception e) {
            log.error("{}.{}方法错误", signature.getDeclaringTypeName(), methodName);
            log.error(e.getMessage(), e);
        }
        return methodInfo;
    }

    /**
     * 执行回调
     * @param callback 回调类
     * @param annotation 触发注解
     * @param methodInfo 方法信息
     * @param joinPoint 切入点
     * @param result 方法结果
     */
    private void callback(
            Class<? extends LogCallback> callback,
            Annotation annotation,
            MethodInfo methodInfo,
            JoinPoint joinPoint,
            Object result
    ) {
        try {
            if (!callback.getName().equals(LogCallback.class.getName())) {
                LogContext.getContext().getBean(callback).callback(
                        annotation,
                        methodInfo,
                        LogHandler.getParamMap(methodInfo.getParamNames(), joinPoint.getArgs()),
                        result
                );
            }else {
                LogContext.getContext().getBean(this.slf4JProperties.getCallback(annotation)).callback(
                        annotation,
                        methodInfo,
                        LogHandler.getParamMap(methodInfo.getParamNames(), joinPoint.getArgs()),
                        result
                );
            }
        }catch (Exception ex) {
            log.error("{}.{}方法日志回调错误：【{}】", methodInfo.getClassAllName(), methodInfo.getMethodName(), ex.getMessage());
        }
    }
}